﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Management;
using System.Drawing;
using System.Drawing.Printing;
using Microsoft.Win32;

namespace UniversalPrinter
{
    /// <summary>
    /// Returns the handled result for printing action.
    /// </summary>
    public enum DocumentPrinterResult : byte
    {
        /// <summary>
        /// Not operated yet.
        /// </summary>
        NOT_SENT_TO_PRINTER = 0,
        /// <summary>
        /// File is sent to printer successfully.
        /// </summary>
        SENT_SUCCESSFULLY = 1,
        /// <summary>
        /// Unhandled error occured.
        /// </summary>
        UNSPECIFIED_ERROR = 2,
        /// <summary>
        /// Any excel application not installed on running machine.
        /// </summary>
        EXCEL_APPLICATION_NOT_FOUND = 3,
        /// <summary>
        /// Newer version of excel required.
        /// </summary>
        INVALID_EXCEL_VERSION = 4,
        /// <summary>
        /// Tried to unsupported file format.
        /// </summary>
        UNSUPPORTED_FILE_FORMAT = 6,
        /// <summary>
        /// The target printer not found on running machine.
        /// </summary>
        PRINTER_NOT_FOUND = 7,
        /// <summary>
        /// Specified file not accessable on running machine.
        /// </summary>
        FILE_NOT_FOUND = 8,
        /// <summary>
        /// Not found any supported PDF file viewer on running machine.
        /// </summary>
        PDF_VIEWER_NOT_FOUND = 10,
        /// <summary>
        /// Not found any supported Text file viewer on running machine.
        /// </summary>
        TEXT_VIEWER_NOT_FOUND = 11
    }

    /// <summary>
    /// A class for the printing jobs.
    /// </summary>
    public class DocumentPrinter : IDisposable
    {
        private List<LocalPrinter> printerList;
        private string PDFCommandPath = null;
        private string PDFCommandArguments = null;
        private string TXTCommandPath = null;
        private string TXTCommandArguments = null;
        private const int ERROR_INSUFFICIENT_BUFFER = 122;

        /// <summary>
        /// Raises after sending each file to printer.
        /// </summary>
        public event EventHandler OnAfterSendPrinter = null;

        #region Constructors
        
        /// <summary>
        /// Initialize and read settings from Windows Registery.
        /// </summary>
        public DocumentPrinter()
        {
            printerList = new List<LocalPrinter>();
            try
            {
                ReadPrintersFromRegistery();
                RegisteryReadForFileExtension(".pdf", out PDFCommandPath, out PDFCommandArguments);
                RegisteryReadForFileExtension(".txt", out TXTCommandPath, out TXTCommandArguments);
            }
            catch (Exception)
            {

                this.printResult = DocumentPrinterResult.UNSPECIFIED_ERROR;
            }
        }

        #endregion

        #region Private Properties
        
        private LocalPrinter Printer
        {
            get
            {
                return printerList.SingleOrDefault(p => p.DeviceID == (this.PrinterName != null ? this.PrinterName : this.DefaultPrinter));
            }
        }

        #endregion

        #region Public Properties
        /// <summary>
        /// User based UniqueID.
        /// </summary>
        public string UniqueID { get; set; }

        /// <summary>
        /// Physical file path of printing document.
        /// </summary>
        public string FileName { get; set; }

        /// <summary>
        /// Set the target printer name.
        /// </summary>
        public string PrinterName { get; set; }

        private DocumentPrinterResult printResult = DocumentPrinterResult.NOT_SENT_TO_PRINTER;

        /// <summary>
        /// Action result for Print method.
        /// </summary>
        public DocumentPrinterResult PrintResult
        {
            get { return printResult; }
        }

        #endregion

        #region Private Methods

        [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool SetDefaultPrinter(string Name);

        [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool GetDefaultPrinter(StringBuilder pszBuffer, ref int pcchBuffer);

        private string DefaultPrinter
        {
            get
            {
                int pcchBuffer = 0;
                if (!GetDefaultPrinter(null, ref pcchBuffer))
                {
                    int lastWin32Error = Marshal.GetLastWin32Error();
                    if (lastWin32Error == ERROR_INSUFFICIENT_BUFFER)
                    {
                        StringBuilder pszBuffer = new StringBuilder(pcchBuffer);
                        if (GetDefaultPrinter(pszBuffer, ref pcchBuffer))
                        {
                            return pszBuffer.ToString();
                        }
                    }
                }
                return null;
            }
        }

        private void ReadPrintersFromRegistery()
        {
            RegistryKey TAWKAY = RegistryKey.OpenRemoteBaseKey(Microsoft.Win32.RegistryHive.CurrentUser, "").OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion\Devices");
            foreach (string regKey in TAWKAY.GetValueNames())
            {
                LocalPrinter printer = new LocalPrinter(regKey);
                string port = TAWKAY.GetValue(regKey).ToString();
                port = port.Substring(port.IndexOf(',') + 1);
                printer.DevicePortAddress = port;
                printerList.Add(printer);
            }
            TAWKAY.Close();
        }

        private void RegisteryReadForFileExtension(string extension, out string filePath, out string arguments)
        {
            string documentKey = null;
            string commandPath = null;
            filePath = null;
            arguments = null;

            RegistryKey TAWKAY = RegistryKey.OpenRemoteBaseKey(Microsoft.Win32.RegistryHive.ClassesRoot, "").OpenSubKey(extension);
                documentKey = System.Convert.ToString(TAWKAY.GetValue(""));
                if (documentKey != null)
                {
                    TAWKAY = RegistryKey.OpenRemoteBaseKey(Microsoft.Win32.RegistryHive.ClassesRoot, "").OpenSubKey(documentKey + @"\shell\printto\command");
                    commandPath = System.Convert.ToString(TAWKAY.GetValue(""));

                    if (commandPath != null)
                    {
                        if (commandPath.IndexOf("/") > 0)
                            filePath = commandPath.Substring(0, commandPath.IndexOf("/"));
                        else
                            filePath = commandPath.Substring(0, commandPath.IndexOf(" "));

                        arguments = commandPath.Substring(filePath.Length).Trim();
                        filePath = filePath.Replace("%SystemRoot%", System.Environment.GetEnvironmentVariable("SystemRoot")).Trim();
                    }
                }
                TAWKAY.Close();
        }

        private void RaiseOnAfterSendPrinter()
        {
            if (OnAfterSendPrinter != null)
                OnAfterSendPrinter(this, EventArgs.Empty);
        }
        #endregion

        #region Public Methods

        /// <summary>
        /// Log WMI informations for local printers.
        /// </summary>
        /// <param name="logFolderPath">Physical directory path of logging informations.</param>
        public void LogPrinterWMIInformations(string logFolderPath)
        {
            ManagementObjectSearcher searchPrinters = new ManagementObjectSearcher("SELECT * FROM Win32_Printer");
            foreach (ManagementObject printer in searchPrinters.Get())
            {
                using (StreamWriter writer = new StreamWriter(logFolderPath + @"\" + Convert.ToString(printer.Properties["DriverName"].Value + ".txt")))
                {
                    foreach (PropertyData data in printer.Properties)
                    {
                        writer.WriteLine(data.Name + " = " + Convert.ToString(data.Value));
                    }
                }
            }
            searchPrinters.Dispose();
        }

        /// <summary>
        /// Logs all printer informations to given path.
        /// </summary>
        /// <param name="filePath">Physical file path to logging.</param>
        public void SavePrinterList(string filePath)
        {
            using (StreamWriter writer = new StreamWriter(filePath))
            {
                foreach (LocalPrinter printer in printerList)
                {
                    writer.WriteLine(printer.ToString());
                }
            }
        }

        /// <summary>
        /// Print given file from default printers.
        /// </summary>
        /// <param name="fileName">Physical file path of printing document.</param>
        public void Print(string fileName)
        {
            this.FileName = fileName;
            this.PrinterName = null;
            this.UniqueID = null;

            Print();
        }

        /// <summary>
        /// Print given file from specific printers.
        /// </summary>
        /// <param name="fileName">Physical file path of printing document.</param>
        /// <param name="printerName">Set the target printer name.</param>
        public void Print(string fileName, string printerName)
        {
            this.FileName = fileName;
            this.PrinterName = printerName;
            this.UniqueID = null;

            Print();
        }

        /// <summary>
        /// Print given file from specific printers with UniqueID.
        /// </summary>
        /// <param name="fileName">Physical file path of printing document.</param>
        /// <param name="printerName">Set the target printer name(set null for using default printer).</param>
        /// <param name="uniqueID">User based UniqueID, useful for cathing file info from OnAfterSendPrinter event.</param>
        public void Print(string fileName, string printerName, string uniqueID)
        {
            this.FileName = fileName;
            this.PrinterName = printerName;
            this.UniqueID = uniqueID;

            Print();
        }

        /// <summary>
        /// Prints a single document, FileName property is required.
        /// </summary>
        public void Print()
        {
            printResult = DocumentPrinterResult.NOT_SENT_TO_PRINTER;
            FileInfo file = new FileInfo(this.FileName);

            if (!file.Exists)
            {
                printResult = DocumentPrinterResult.FILE_NOT_FOUND;
            }

            if (printResult == DocumentPrinterResult.NOT_SENT_TO_PRINTER && this.Printer == null)
            {
                printResult = DocumentPrinterResult.PRINTER_NOT_FOUND;
            }

            if (printResult == DocumentPrinterResult.NOT_SENT_TO_PRINTER)
            {
                try
                {
                    string extension = file.Extension.ToLower();

                    switch (extension)
                    {
                        case ".xls":
                        case ".xlsx":
                            {
                                System.Globalization.CultureInfo oldCI = System.Threading.Thread.CurrentThread.CurrentCulture;
                                System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");

                                using (MSExcel objExcel = new MSExcel())
                                {
                                    if (objExcel.ExcelResult == 3)
                                    {
                                        printResult = DocumentPrinterResult.EXCEL_APPLICATION_NOT_FOUND;
                                    }
                                    else if (objExcel.ExcelResult == 0 && objExcel.Version == "12.0")
                                    {
                                        objExcel.Open(file.FullName);
                                        objExcel.Print(this.Printer.DeviceID);
                                        printResult = DocumentPrinterResult.SENT_SUCCESSFULLY;
                                    }
                                    else
                                    {
                                        printResult = DocumentPrinterResult.INVALID_EXCEL_VERSION;
                                    }
                                }

                                System.Threading.Thread.CurrentThread.CurrentCulture = oldCI;

                                break;
                            }
                        case ".as":
                        case ".asa":
                        case ".asax":
                        case ".ascx":
                        case ".aspx":
                        case ".bat":
                        case ".c":
                        case ".cpp":
                        case ".cs":
                        case ".css":
                        case ".dtd":
                        case ".inf":
                        case ".ini":
                        case ".js":
                        case ".php":
                        case ".sql":
                        case ".txt":
                        case ".vb":
                        case ".vbs":
                        case ".xaml":
                        case ".xml":
                        case ".xsd":
                            {
                                if (TXTCommandPath == null)
                                {
                                    printResult = DocumentPrinterResult.TEXT_VIEWER_NOT_FOUND;
                                }
                                else
                                {
                                    Process printer = new Process();
                                    printer.StartInfo.UseShellExecute = true;
                                    printer.StartInfo.CreateNoWindow = true;
                                    printer.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
                                    printer.StartInfo.Arguments = TXTCommandArguments.Replace("%1", file.FullName).Replace("%2", this.Printer.DeviceID).Replace(" \"%3\"", string.Empty).Replace(" \"%4\"", string.Empty);
                                    printer.StartInfo.FileName = TXTCommandPath;
                                    printer.Start();

                                    printResult = DocumentPrinterResult.SENT_SUCCESSFULLY;
                                }
                                break;
                            }
                        case ".pdf":
                            {
                                if (PDFCommandPath == null)
                                {
                                    printResult = DocumentPrinterResult.PDF_VIEWER_NOT_FOUND;
                                }
                                else
                                {
                                    Process printer = new Process();
                                    printer.StartInfo.UseShellExecute = true;
                                    printer.StartInfo.CreateNoWindow = true;
                                    printer.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
                                    printer.StartInfo.Arguments = PDFCommandArguments.Replace("%1", file.FullName).Replace("%2", this.Printer.DeviceID).Replace(" \"%3\"", string.Empty).Replace(" \"%4\"", string.Empty);
                                    printer.StartInfo.FileName = PDFCommandPath;
                                    printer.Start();

                                    printResult = DocumentPrinterResult.SENT_SUCCESSFULLY;
                                }
                                break;
                            }
                        case ".jpg":
                        case ".jpe":
                        case ".jpeg":
                        case ".gif":
                        case ".png":
                        case ".bmp":
                        case ".tif":
                        case ".tiff":
                            {
                                using (PrintDocument printer = new PrintDocument())
                                {
                                    printer.PrintPage += delegate(object sender, PrintPageEventArgs e)
                                    {
                                        Graphics g = e.Graphics;
                                        g.DrawImage(Image.FromFile(file.FullName), printer.DefaultPageSettings.Margins.Left, printer.DefaultPageSettings.Margins.Top);
                                    };

                                    printer.PrinterSettings.PrinterName = this.Printer.DeviceID;
                                    printer.Print();
                                }

                                printResult = DocumentPrinterResult.SENT_SUCCESSFULLY;
                                break;
                            }
                        case ".prn":
                            {
                                Process printer = new Process();
                                printer.StartInfo.UseShellExecute = true;
                                printer.StartInfo.Arguments = "/C COPY /B \"" + this.FileName + "\" \"" + this.Printer.DeviceID + "\"";
                                printer.StartInfo.CreateNoWindow = true;
                                printer.StartInfo.Verb = "";
                                printer.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
                                printer.StartInfo.FileName = "CMD.EXE";
                                printer.Start();

                                printResult = DocumentPrinterResult.SENT_SUCCESSFULLY;
                                break;
                            }
                        case ".htm":
                        case ".html":
                            {
                                lock (this)
                                {
                                    if (this.PrinterName != null)
                                    {
                                        SetDefaultPrinter(this.Printer.DeviceID);
                                    }

                                    HTMLPrinter printer = new HTMLPrinter();
                                    printer.Print(file.FullName);
                                }

                                printResult = DocumentPrinterResult.SENT_SUCCESSFULLY;
                                break;
                            }
                        default:
                            printResult = DocumentPrinterResult.UNSUPPORTED_FILE_FORMAT;
                            break;
                    }
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    printResult = DocumentPrinterResult.UNSPECIFIED_ERROR;
                }
            }

            RaiseOnAfterSendPrinter();
        }

        /// <summary>
        /// Release the used resources.
        /// </summary>
        public void Dispose()
        {
            printerList = null;
            PDFCommandPath = null;
            PDFCommandArguments = null;
            TXTCommandPath = null;
            TXTCommandArguments = null;
            OnAfterSendPrinter = null;
        }

        #endregion
    }
}
